using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using Tycho.Parser.Peg;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class ProductionSpecification {
        [Test]
        public void ShouldInvokeResultTransformOnSuccessfulParse () {
            var context = new ParseContext ();
            var result = new ParseResult (1, null, context);
            var production = new ProductionTestExtension (result);
            var newResult = new ParseResult (600, null, context);

            production.ResultTransform = r => {
                                             Assert.That (r, Is.Not.Null);
                                             return newResult;
                                         };

            Assert.That (production.Parse ("blah", 0, null), Is.SameAs (newResult));
        }

        [Test]
        public void ShouldNotInvokeResultTransformOnUnsuccessfulParse () {
            var production = new ProductionTestExtension (null);

            production.ResultTransform = r => {
                                             Assert.Fail ("this should not be called");
                                             return r;
                                         };

            Assert.That (production.Parse ("blah", 0, null), Is.Null);
        }

        class RecursiveProduction : Production {
            protected override ParseResult ReallyParse (char[] source, int index, ParseContext context, string sourceString, ParseEnvironment parseEnvironment) {
                return Parse (source, index, context, sourceString, parseEnvironment);
            }
        }

        [Test]
        public void ParseShouldFailIfRecursiveParseDetected () {
            Assert.IsNull (new RecursiveProduction ().Parse ("abc", 0, null));
        }

        public class FixedWidthStringProduction : Production {
            private int Width;

            public FixedWidthStringProduction (int width) {
                Width = width;
            }

            protected override ParseResult ReallyParse (char[] source, int index, ParseContext context, string sourceString, ParseEnvironment parseEnvironment) {
                if (source.Length >= index + Width) {
                    return new ParseResult (index + Width, new StringTerm (sourceString.Substring (index, Width), null),
                                            context);
                } else {
                    return null;
                }
            }
        }

        [Test]
        public void ParseShouldNotRememberPreviousParse () {
            var production = new FixedWidthStringProduction (3);

            AssertParsesFixedWidthString (production, "123");
            AssertParsesFixedWidthString (production, "456");
        }

        [Test]
        public void ParseShouldAddSyntaxErrorIfReallyParseReturnsNull () {
            var failingProduction = new FailingProduction ();
            var parseEnvironment = new ParseEnvironment ("filename");
            var errorLogger = new Mock<ISyntaxErrorLogger> ();
            const string source = "anything";

            parseEnvironment.SyntaxErrors = errorLogger.Object;
            parseEnvironment.LeftRecursionInformation = new LeftRecursionInformation ();
            parseEnvironment.SyntaxErrorRecoverer = new Mock<ISyntaxErrorRecoverer> ().Object;

            var context = new ParseContext();
            failingProduction.Parse (source.ToCharArray(), 0, context, source, parseEnvironment);

            errorLogger.Verify(e => e.LogSyntaxError (0, failingProduction, context, It.IsAny<Func<IEnumerable<ProductionInvocation>>> ()));
        }

        private static void AssertParsesFixedWidthString (IProduction production, string parsedString) {
            ITerm term = production.ParseTerm (parsedString);

            Assert.That (term, Is.Not.Null);
            Assert.That (term, Is.InstanceOf (typeof (StringTerm)));
            var stringTerm = (StringTerm) term;
            Assert.That (stringTerm.Value, Is.EqualTo(parsedString));
        }
    }
}